gh-153056: Fix a data race compiling the string.Template pattern in free-threading builds#153057
Conversation
…n in free-threading builds Template compiles its substitution pattern lazily and caches it on the class. On the free-threaded build two concurrent first uses could race: a thread that observed the pattern another thread had just compiled would try to recompile it, and re.compile() rejects flags on an already-compiled pattern, raising a spurious ValueError. Return the already-compiled pattern instead. As a side effect, a subclass that supplies an already-compiled pattern now works too; previously it raised the same ValueError at class definition.
|
I am not very fond of that but I don't see a better way of doing it except by allowing flags on a compiled pattern assuming those flags are the same. Are the flags retained by a pattern object or not? if they are not, I don't see another solution than the proposed one. |
|
It's actually a little more complicated because of what the docs actually say:
"regular expression object" implies a compiled regular expression, but that actually breaks in exactly the way you've identified in your "non-free-threading" comments and test. So the very thing that the docs tell you to do is unsupported, and I believe nothing actually tests that; all the tests set So your fix is correct on that basis alone. Bonus that it fixes the FT race condition! I'll take a closer look at the diff exactly, but since you're here, would you mind adding an update to the docs to describe that |
warsaw
left a comment
There was a problem hiding this comment.
Thanks for finding and fixing this! As mentioned, I believe this is a legitimate bug regardless of the FT race, so it's good to fix and back port. Please do add a doc fix and ping me for a re-review when you're ready, as the bot will instruct.
|
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase |
|
Updated the docs for I have made the requested changes; please review again |
|
Thanks for making the requested changes! @warsaw: please review the changes made to this pull request. |
|
Also addressed the inline comments: added the comment on the three states of I have made the requested changes; please review again |
|
Thanks for making the requested changes! @warsaw: please review the changes made to this pull request. |
Documentation build overview
48 files changed ·
|
warsaw
left a comment
There was a problem hiding this comment.
I made one small wording suggestion, but otherwise LGTM! I'm going to update the branch and add the backport tags.
|
Thanks @tonghuaroot for the PR, and @warsaw for merging it 🌮🎉.. I'm working now to backport this PR to: 3.13, 3.14, 3.15. |
|
GH-153302 is a backport of this pull request to the 3.15 branch. |
|
Sorry, @tonghuaroot and @warsaw, I could not cleanly backport this to |
|
GH-153303 is a backport of this pull request to the 3.14 branch. |
…rn in free-threading builds (GH-153057) (#153302) gh-153056: Fix a data race compiling the string.Template pattern in free-threading builds (GH-153057) * gh-153056: Fix a data race compiling the string.Template pattern in free-threading builds Template compiles its substitution pattern lazily and caches it on the class. On the free-threaded build two concurrent first uses could race: a thread that observed the pattern another thread had just compiled would try to recompile it, and re.compile() rejects flags on an already-compiled pattern, raising a spurious ValueError. Return the already-compiled pattern instead. As a side effect, a subclass that supplies an already-compiled pattern now works too; previously it raised the same ValueError at class definition. * Trim test comments and NEWS wording * Document that the pattern attribute accepts a string or a compiled regex * Comment the three states of pattern and note the documented-behavior fix in NEWS * Update Doc/library/string.rst --------- (cherry picked from commit 4572903) Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com> Co-authored-by: Barry Warsaw <barry@python.org>
|
Thanks. I looked into the 3.13 backport and I do not think it is warranted. On 3.13, The only 3.13-relevant part would be the secondary fix, where a subclass that supplies an already-compiled |
…rn in free-threading builds (GH-153057) (#153303) gh-153056: Fix a data race compiling the string.Template pattern in free-threading builds (GH-153057) * gh-153056: Fix a data race compiling the string.Template pattern in free-threading builds Template compiles its substitution pattern lazily and caches it on the class. On the free-threaded build two concurrent first uses could race: a thread that observed the pattern another thread had just compiled would try to recompile it, and re.compile() rejects flags on an already-compiled pattern, raising a spurious ValueError. Return the already-compiled pattern instead. As a side effect, a subclass that supplies an already-compiled pattern now works too; previously it raised the same ValueError at class definition. * Trim test comments and NEWS wording * Document that the pattern attribute accepts a string or a compiled regex * Comment the three states of pattern and note the documented-behavior fix in NEWS * Update Doc/library/string.rst --------- (cherry picked from commit 4572903) Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com> Co-authored-by: Barry Warsaw <barry@python.org>
Thanks for looking into this. I think it's still worth backporting the regex object support fix and it wasn't that hard to cherry pick and resolve. |
Fixes the free-threading data race in the linked issue.
Template._compile_pattern()could read a pattern that another thread had just compiled and cached, then hand that already-compiledre.Patterntore.compile(pattern, cls.flags | re.VERBOSE), which rejects a flags argument on a compiled pattern and raises a spuriousValueError. The fix returns the already-compiled pattern instead, which is idempotent and lock-free.Testing
Lib/test/test_free_threading/test_string_template_race.pyraces the lazy compile across many threads withthreading_helper.run_concurrently(barrier-synchronized). It uses a throwaway subclass re-armed to the uncompiled sentinel each round, so the sharedstring.Templateclass is never mutated. Without the fix, the first round raises theValueErroron many of the racing threads; with it, 0.Lib/test/test_string/test_string.py::TestTemplate::test_precompiled_patternis a deterministic, non-threaded regression that runs on every build: a subclass supplying an already-compiled pattern raised theValueErrorat class definition before the fix and works after it.Notes
re.Patterntopattern, where it previously raised at class definition. The commit message calls this out as a deliberate consequence rather than leaving it implicit.cls.flags(two threads may both storere.IGNORECASE); it does not affect correctness and is intentionally left out of scope to keep this change single-purpose.